package csel.controller;

import java.io.IOException;
import csel.controller.bindings.*;
import csel.controller.commands.*;
import csel.controller.commandutil.*;
import csel.controller.decoders.*;
import csel.controller.dispatchers.*;
import csel.controller.enums.*;
import csel.controller.events.*;
import csel.controller.listeners.*;

import csel.model.GameCommand;
import csel.model.Model;
import csel.view.GameViewport;
import csel.view.Splasher;

/**
 * This is the main class that is used to contruct the various parts of the
 * controller
 *  
 * @author Nikolas Wolfe
 */
public class Controller implements Bindable
{
	//=========================================================================
	// Refrences
	private final Model model;
	private final Splasher splasher;
	private CommandPool commandPool;
	private GameViewport gameViewport;
	
	//=========================================================================
	// Binding Classes
	private ActionBindings actionBindings;
	private KeyBindings keyBindings;
	private ActionCommandBindings actionCommandBindings;
	
	//=========================================================================
	// Dispatchers
	private final Dispatcher dispatcher;
	private Dispatcher splasherDispatcher;
	//private Dispatcher modelDispatcher;
	//private Dispatcher gameViewportDispatcher;
	
	//=========================================================================
	// Decoders
	private EventDecoder<Action> splasherActionDecoder;
	private EventDecoder<Action> gameViewportActionDecoder;
	private EventDecoder<KeyPress> gameViewportKeyDecoder;
	
	//=========================================================================
	// Listeners
	private ActionEventListener splasherActionListener;
	private ActionEventListener gameViewportActionListener;
	private KeyboardListener gameViewportKeyboardListener;
	
	//=========================================================================
	// KeyBinding IO
	private BindingModifier bindingModifier;
	
	/**
	 * ========================================================================
	 * constructor
	 */
	public Controller( Model model, Splasher splasher )
	{
		this.model = model;
		this.splasher = splasher;
		this.dispatcher = new Dispatcher( model );
		this.commandPool = CommandPool.getInstance();
	}
	
	/**
	 * ========================================================================
	 * construct() -- must be called before all else
	 */
	public void construct()
	{
		// create binding classes
		this.actionBindings = ActionBindings.getInstance();
		this.keyBindings = KeyBindings.getInstance();
		this.actionCommandBindings = ActionCommandBindings.getInstance();
		this.bindingModifier = new BindingModifier(keyBindings);
		
		buildSplasher();
		
		createInitiallyAvailableSimpleCommands();
		createInitiallyAvailableParameterizableCommands();
	}
	
	/**
	 * ========================================================================
	 * destroySplasher() 
	 */
	public void destroySplasher()
	{
		assertSanity();
		if( splasherActionListener == null )
			throw new IllegalStateException("Splasher not initialized!");
		
		// hide this fucker
		splasher.setVisible(false);
		
		// unregister listener
		splasher.removeActionListener( splasherActionListener );
		
		// kill threads
		splasherDispatcher.stopDispatcher();
		splasherActionListener.stopDecoder();
		
		// end this madness
		splasher.close();
	}
	
	/**
	 * ========================================================================
	 * destroyGameViewport() -- WARNING: THE GAME ENDS HERE
	 */
	public void destroyGameViewport()
	{
		assertSanity();
		if( gameViewport == null )
			throw new IllegalStateException("Game Viewport not initialized!");
		
		// hide viewport
		gameViewport.setVisible( false );
		
		// unregister listeners
		gameViewport.removeActionListener( gameViewportActionListener );
		gameViewport.removeKeyListener( gameViewportKeyboardListener );
		
		// kill threads
		dispatcher.stopDispatcher();
		gameViewportActionListener.stopDecoder();
		gameViewportKeyboardListener.stopDecoder();
		
		// end this madness
		gameViewport.close();
		model.stopGame();
	}
	
	/**
	 * ========================================================================
	 * createGameViewPort() -- builds the game viewport, attaches the 
	 * appropriate listeners
	 */
	public void createGameViewport()
	{
		assertSanity(); // must be called in all public methods
		buildGameViewport();
		createGameRunningSimpleCommands();
		createGameRunningParameterizableCommands();
	}

	/**
	 * ========================================================================
	 * This will stop the game from the main splash screen
	 */
	public void splashQuit()
	{
		destroySplasher();
		model.stopGame();
	}
		
	/**
	 * ========================================================================
	 * buildSplasher() 
	 */
	private void buildSplasher()
	{
		// construct the splash screen
		splasherDispatcher = new Dispatcher( splasher );
		splasherActionDecoder = new ActionEventDecoder( commandPool, splasherDispatcher );
		splasherActionListener = new ActionEventListener( splasherActionDecoder, actionBindings );
		
		// register the listener
		splasher.addActionListener(splasherActionListener);
		
		// start threads
		splasherDispatcher.startDispatcher();
		splasherActionListener.startDecoder();
		
		// display the splasher
		splasher.setVisible(true);
	}
	
	/**
	 * ========================================================================
	 * buildGameViewport() 
	 */
	private void buildGameViewport()
	{
		// construct Game Viewport
		this.gameViewport = new GameViewport( model );
		
		gameViewportActionDecoder = new ActionEventDecoder( commandPool, dispatcher );
		gameViewportKeyDecoder = new KeyEventDecoder( commandPool, dispatcher );
		
		gameViewportActionListener = new ActionEventListener( gameViewportActionDecoder, actionBindings );
		gameViewportKeyboardListener = new KeyboardListener( gameViewportKeyDecoder, keyBindings );
		
		// add the listeners
		gameViewport.addActionListener( gameViewportActionListener );
		gameViewport.addKeyboardListener( gameViewportKeyboardListener );
		
		// launch primary dispatcher
		dispatcher.startDispatcher();
		gameViewportActionListener.startDecoder();
		gameViewportKeyboardListener.startDecoder();
		
		// make it visible
		gameViewport.setVisible(true);
	}
	
	/**
	 * ========================================================================
	 * createInitiallyAvailableSimpleCommands() -- Creates the list of initially 
	 * available command objects -- called at the beginning of the game
	 */
	private void createInitiallyAvailableSimpleCommands()
	{
		// ==============================================================
		// COMMANDS AVAILABLE AT STARTUP. ADD TO THIS LIST WHEN NECESSARY
		// ==============================================================
		
		// Splash New Game Command
		final SPNewGameCommand spNewGameCommand = new SPNewGameCommand(model, this);
		registerSimpleCommand(CommandEnum.SP_NEWGAME_COMMAND, spNewGameCommand);
		
		// Splash Quit Game Command
		final SPQuitCommand spQuitCommmand = new SPQuitCommand(this);
		registerSimpleCommand(CommandEnum.SP_QUIT_COMMAND, spQuitCommmand);
		
		// Splash Load Game Command
		final SPLoadGameCommand spLoadCommand = new SPLoadGameCommand(model, splasher, this);
		registerSimpleCommand(CommandEnum.SP_LOAD_COMMAND, spLoadCommand);
	}
	
	/**
	 * ========================================================================
	 * createInitiallyAvailableParameterizableCommands() -- Creates the list of 
	 * initially available command objects -- called at the beginning of the game
	 */
	private void createInitiallyAvailableParameterizableCommands()
	{
		// ==============================================================
		// COMMANDS AVAILABLE AT STARTUP. ADD TO THIS LIST WHEN NECESSARY
		// ==============================================================
	}
	
	/**
	 * ========================================================================
	 * createGameRunningSimpleCommands() -- Creates the list of game commands
	 * available during the game 
	 */
	private void createGameRunningSimpleCommands()
	{
		// ==============================================================
		// COMMANDS AVAILABLE AT STARTUP. ADD TO THIS LIST WHEN NECESSARY
		// ==============================================================
		
		// Stop Command
		final StopCommand stCmd = new StopCommand(model);
		registerSimpleCommand(CommandEnum.STOP_COMMAND, stCmd);
		
		// Attack Command
		final AttackCommand atCmd = new AttackCommand(model, gameViewport);
		registerSimpleCommand(CommandEnum.ATTACK_COMMAND, atCmd);
		
		final GVChangeSubviewCommand subCmd = new GVChangeSubviewCommand(gameViewport);
		registerSimpleCommand(CommandEnum.GV_SWITCH_SUBVIEW, subCmd );
		
		final GVRotateSlotsCommand rotCmd = new GVRotateSlotsCommand(gameViewport);
		registerSimpleCommand(CommandEnum.GV_ROTATE_SLOTS, rotCmd );
		
		final GVSwitchToEquipmentCommand equCmd = new GVSwitchToEquipmentCommand(gameViewport);
		registerSimpleCommand(CommandEnum.GV_SWITCH_EQUIPMENT, equCmd);
		
		final GVSwitchToSkillsCommand skiCmd = new GVSwitchToSkillsCommand(gameViewport);
		registerSimpleCommand(CommandEnum.GV_SWITCH_SKILLS, skiCmd);
		
		final GVSwitchToInventoryCommand invCmd = new GVSwitchToInventoryCommand(gameViewport);
		registerSimpleCommand(CommandEnum.GV_SWITCH_INVENTORY, invCmd);
		
		final GVToggleMenuCommand togCmd = new GVToggleMenuCommand(gameViewport);
		registerSimpleCommand(CommandEnum.GV_TOGGLE_MENU_COMMAND, togCmd);
		
		final ShowChangeBindingsDialog showChangeBindingsDialog = new ShowChangeBindingsDialog(gameViewport, this);
		registerSimpleCommand(CommandEnum.CHANGE_BINDINGS_SHOW, showChangeBindingsDialog);
		
		final HideDialogCommand hideDialogCommand = new HideDialogCommand(gameViewport);
		registerSimpleCommand(CommandEnum.HIDE_DIALOG, hideDialogCommand);
		
		final BMNewGameCommand bmnewCmd = new BMNewGameCommand(model);
		registerSimpleCommand(CommandEnum.BM_NEW_GAME_COMMAND, bmnewCmd);
		
		final BMLoadGameCommand bmldCmd = new BMLoadGameCommand(model, gameViewport, this);
		registerSimpleCommand(CommandEnum.BM_LOAD_COMMAND, bmldCmd);
		
		final BMSaveGameCommand bmsaveCmd = new BMSaveGameCommand(model, gameViewport, this);
		registerSimpleCommand(CommandEnum.BM_SAVE_COMMAND, bmsaveCmd);
		
		final BMQuitCommand bmquitCmd = new BMQuitCommand(this);
		registerSimpleCommand(CommandEnum.BM_QUIT_COMMAND, bmquitCmd);
		
		final BMChangeControlsCommand bmChngCntrlsCmd = new BMChangeControlsCommand(gameViewport, this);
		registerSimpleCommand(CommandEnum.BM_CHANGE_CONTROLS_COMMAND, bmChngCntrlsCmd);
		
		final TradeDialogCommand tradeDialogCommand = new TradeDialogCommand(gameViewport, model);
		registerSimpleCommand(CommandEnum.SHOW_TRADE_DIALOG_COMMAND, tradeDialogCommand );
		
		final BMUpgradeSkillCommand bmskupCmd = new BMUpgradeSkillCommand(model, gameViewport);
		registerSimpleCommand(CommandEnum.BM_SKILLS_UPGRADE_COMMAND,bmskupCmd);
		
		final BMUseOnSelfSkillCommand bmuseonselfCmd = new BMUseOnSelfSkillCommand(model, gameViewport);
		registerSimpleCommand(CommandEnum.BM_SKILLS_USESELF_COMMAND, bmuseonselfCmd);
		
		final BMUseInDirectionCommand bmuseindirCmd = new BMUseInDirectionCommand(model, gameViewport);
		registerSimpleCommand(CommandEnum.BM_SKILLS_USEOTHER_COMMAND, bmuseindirCmd);
		
		final BuyCommand buyCmd = new BuyCommand(model, gameViewport);
		registerSimpleCommand(CommandEnum.BUY_COMMAND, buyCmd);
		
		final SellCommand sellCmd = new SellCommand(model, gameViewport);
		registerSimpleCommand(CommandEnum.SELL_COMMAND, sellCmd);
		
		final LeaveVehicleCommand lvVehCmd = new LeaveVehicleCommand(model);
		registerSimpleCommand(CommandEnum.LEAVE_VEHICLE_COMMAND, lvVehCmd);
	}
	
	/**
	 * ========================================================================
	 * createGameRunningParameterizableCommands() -- Creates the list of game commands
	 * available during the game 
	 */
	private void createGameRunningParameterizableCommands()
	{
		// ==============================================================
		// PARAMETERIZED GAME COMMANDS
		// ==============================================================
		
		// Move Command
		final MoveCommand moveCommand = new MoveCommand(model);
		registerParameterizableCommand(CommandEnum.MOVE_COMMAND, moveCommand);
		
		// Number Bar Commands -- Drop Equippable
		final NBDropEquippableItemCommand dropEquippableItemCommand = new NBDropEquippableItemCommand(model, gameViewport);
		registerParameterizableCommand(CommandEnum.NB_INVENTORY_DROP_EQUIPPABLE, dropEquippableItemCommand);
		
		// Number Bar Commands -- Drop Usable
		final NBDropUseableItemCommand dropUseableItemCommand = new NBDropUseableItemCommand(model, gameViewport);
		registerParameterizableCommand(CommandEnum.NB_INVENTORY_DROP_USEABLE, dropUseableItemCommand);
		
		// Number Bar Commands -- Equip
		final NBEquipCommand equipCommand = new NBEquipCommand(model, gameViewport);
		registerParameterizableCommand(CommandEnum.NB_INVENTORY_EQUIP, equipCommand);
		
		// Number Bar Commands -- Throw 
		final NBThrowCommand throwCommand = new NBThrowCommand(model, gameViewport);
		registerParameterizableCommand(CommandEnum.NB_INVENTORY_THROW, throwCommand);
		
		// Number Bar Commands -- Unequip 
		final NBUnequipCommand unequipCommand = new NBUnequipCommand(model);
		registerParameterizableCommand(CommandEnum.NB_EQUIPMENT_UNEQUIP, unequipCommand);
		
		// Number Bar Commands -- Upgrade Skill  
		final NBUpgradeSkillCommand upgradeSkillCommand= new NBUpgradeSkillCommand(model, gameViewport);
		registerParameterizableCommand(CommandEnum.NB_SKILLS_UPGRADE, upgradeSkillCommand);
		
		// Number Bar Commands -- Use In Direction Skill  
		final NBUseInDirectionSkillCommand useInDirectionSkillCommand = new NBUseInDirectionSkillCommand(model, gameViewport);
		registerParameterizableCommand(CommandEnum.NB_SKILLS_PROJECT, useInDirectionSkillCommand);
		
		// Number Bar Commands -- Use item  
		final NBUseItemCommand useItemCommand = new NBUseItemCommand(model, gameViewport);
		registerParameterizableCommand(CommandEnum.NB_INVENTORY_USE, useItemCommand);
		
		// Number Bar Commands -- Use on self  
		final NBUseOnSelfSkillCommand onSelfSkillCommand = new NBUseOnSelfSkillCommand(model, gameViewport);
		registerParameterizableCommand(CommandEnum.NB_SKILLS_USE, onSelfSkillCommand);
	}
	
	/**
	 * ========================================================================
	 * registerSimpleCommand() -- add to the pool
	 */
	private void registerSimpleCommand(CommandEnum name, final GameCommand gc)
	{
		CommandFactory factory = new SimpleCommandFactory( gc );
		this.commandPool.addCommandFactory(name, factory);
	}
	
	/**
	 * ========================================================================
	 * registerParameterizableCommand() -- add to the pool
	 */
	private void registerParameterizableCommand(CommandEnum name, final Parameterizable p)
	{
		CommandFactory factory = new ParameterizableCommandFactory( p );
		this.commandPool.addCommandFactory(name, factory);
	}
	
	/**
	 * ========================================================================
	 * assertSanity() -- make sure the game has been initialized before you 
	 * do anything rash!
	 */
	private void assertSanity()
	{
		if(	(actionBindings == null) ||
			(keyBindings == null) ||
			(actionCommandBindings == null))
		{
			throw new IllegalStateException( 
					"Controller not properly initialized!" );
		}
	}

	public void changeBinding(Integer virtualKey, ActionEnum actionEnum)
	{
		assertSanity();
		bindingModifier.changeBinding(virtualKey, actionEnum);
	}
	
	public void loadBindings(String fileName) throws IOException
	{
		assertSanity();
		bindingModifier.loadBindings(fileName);
	}

	public void saveBindings(String fileName) throws IOException
	{
		assertSanity();
		bindingModifier.saveBindings(fileName);
	}
}
